<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>诡影寻踪辅助工具</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

    <meta name="apple-mobile-web-app-title" content="诡影寻踪辅助工具" />
    <link rel="apple-touch-icon" href="./icon1.png" />

    <!-- <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.16/vue.js"></script> -->
    <script src="./vue.js"></script>
    <style>
      /* 基础布局 */
      html,
      body,
      #app {
        margin: 0;
        padding: 0;
        height: 100%;
      }
      :root {
        --player-alpha: #b53c3d;
        --player-beta: #00b37f;
        --player-gamma: #02b3e1;
        --player-delta: #ffd600;
        --player-epsilon: #7030d2;
      }

      /* 遮罩层样式 */
      .fullscreen-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.9);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 1001;
        flex-direction: column;
      }
      .fullscreen-overlay .ios-tip {
        color: #fff;
        font-size: 1.5rem;
        padding: 10px;
        text-align: center;
      }
      /* 开始按钮样式 */
      .fullscreen-overlay .btn {
        padding: 15px 30px;
        font-size: 1.5rem;
        background: #2196f3;
        color: white;
        border: none;
        border-radius: 25px;
        cursor: pointer;
        transition: transform 0.2s;
      }

      .fullscreen-overlay .btn:active {
        transform: scale(0.95);
      }

      /* 在样式表中添加 */
      .alert-warning {
        position: fixed;
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
        background: #ffeb3b;
        padding: 15px 25px;
        border-radius: 8px;
        box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
        z-index: 3000;
      }
      .setp1-container {
        align-items: center;
        display: flex;
        height: 100vh;
        padding: 10px;
        box-sizing: border-box;
        justify-content: space-evenly;
      }

      .grid {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        flex: 0 0 auto; /* 禁止伸缩 */
        aspect-ratio: 353.5 / 308.08; /* 宽高比 */
        height: 100%;
      }
      /* 兼容性兜底（旧版浏览器） */
      @supports not (aspect-ratio: 1/1) {
        .grid {
          /* 传统 padding-top 方式实现宽高比 */
          height: 100vh;
          width: calc(100vh * (353.5 / 308.08));
          position: relative;
        }
      }

      .grid-item {
        width: 100%; /* 宽度自由设置（响应式） */
        position: relative;
        aspect-ratio: 1.56;
        overflow: hidden;
        /* 隐藏边框 */
        border: none !important;
      }
      /* 负边距设置 */
      .grid-item:nth-child(n + 3) {
        /* 第二行及之后的格子 */
        margin-top: -9%;
      }
      .grid-item:nth-child(even) {
        /* 第二列 */
        margin-left: -6%;
      }

      .map-tile {
        width: 100%;
        height: 100%;
        background-size: contain;
        background-repeat: no-repeat;
        background-position: center;
        transition: transform 0.3s ease;
      }

      .controls {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        display: flex;
        gap: 15px;
        z-index: 2;
        opacity: 0.9;
        transition: opacity 0.3s;
      }

      .controls:hover {
        opacity: 1;
      }

      .controls button {
        width: 68px;
        height: 30px;
        font-size: 16px;
        border-radius: 8px;
        border: none;
        cursor: pointer;
        transition: all 0.3s;
        box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
        display: flex;
        align-items: center;
        justify-content: center;
        white-space: nowrap;
        overflow: visible;
      }

      .controls button:nth-child(1) {
        /* 旋转按钮 */
        background: #4caf50;
        color: white;
      }

      .controls button:nth-child(2) {
        /* 删除按钮 */
        background: #ff4444;
        color: white;
      }

      .controls button:hover {
        transform: translateY(-2px);
        box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
      }

      .controls button:active {
        transform: translateY(1px);
      }

      /* 专家模式切换按钮 */
      .expert-toggle {
        position: absolute;
        right: 5%;
        top: 5%;
        display: flex;
        align-items: center;
        gap: 8px;
      }

      .expert-toggle label {
        cursor: pointer;
      }
      .act-btn {
        min-width: 85px;
        max-width: 125px;
        padding: 15px;
        background-color: #4caf50;
        color: white;
        border: none;
        border-radius: 8px;
        cursor: pointer;
        font-size: 16px;
      }

      .act-btn:disabled {
        background-color: #ccc;
        cursor: not-allowed;
      }
      .dialog-content .act-btn {
        position: absolute;
        right: 2%;
        bottom: 5%;
      }
      .start-btn {
        position: absolute;
        right: 1%;
        top: 50%;
      }
      .map-container {
        margin: auto;
        height: 100vh;
        aspect-ratio: 10/9;
      }

      .map-row {
        display: flex;
        justify-content: center;
        margin-bottom: 1px; /* 六边形重叠效果 */
      }

      /* 六边形容器 - 控制宽高比 */
      .hex-container {
        width: 8.33%;
        position: relative;
        flex-grow: 1;
        pointer-events: auto; /* 容器响应事件 */
      }
      /* 第二列及之后的格子 */
      .hex-container:nth-child(n + 2) {
        margin-left: -2.6%;
      }
      .hex-container:nth-child(even) .hexagon {
        margin-top: 44%;
      }

      /* 通过 padding-top 维持高宽比例 */
      .hex-container::after {
        content: '';
        display: block;
        padding-top: 86.6%; /* 高度 = 宽度 × (√3/2) ≈ 86.6%*/
        pointer-events: none; /* 新增这行 */
      }

      /* 六边形本体 */
      .hexagon {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: #6c6;
        clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
      }

      .hexagon.impossible {
        opacity: 0.2;
        box-shadow: inset 0 0 8px 5px rgb(255 0 0 / 80%);
      }

      /* 地形颜色 */
      .hexagon.forest {
        background: #018644;
      }
      .hexagon.ocean {
        background: #016099;
      }
      .hexagon.desert {
        background: #ccab09;
      }
      .hexagon.mountain {
        background: #868c8c;
      }
      .hexagon.marsh {
        background: #43243d;
      }

      /* 领地标记 */
      .fox-icon,
      .bear-icon {
        position: absolute;
        font-size: 15px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
      .selector-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 1000;
      }

      .selector-content {
        background: white;
        padding: 20px;
        border-radius: 10px;
        width: 80%;
        max-width: 400px;
        position: relative;
      }

      .tile-options {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 10px;
        margin-top: 15px;
      }

      .tile-option {
        height: 80px;
        border: 2px solid #666;
        border-radius: 8px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 24px;
        font-weight: bold;
        cursor: pointer;
        background-size: contain;
        background-repeat: no-repeat;
        background-position: center;
        position: relative;
        transition: all 0.2s ease;
      }

      .tile-number {
        background: rgba(255, 255, 255, 0.8);
        padding: 2px 8px;
        border-radius: 4px;
        position: absolute;
        top: 5px;
        left: 5px;
      }

      .close-btn {
        position: absolute;
        top: 10px;
        right: 10px;
        padding: 5px 10px;
        border: none;
        background: #ff4444;
        color: white;
        border-radius: 4px;
        cursor: pointer;
      }

      .add-button {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: auto;
        min-width: 120px;
        padding: 16px 24px;
        border: 2px dashed #6c757d;
        border-radius: 12px;
        background: rgba(255, 255, 255, 0.9);
        color: #495057;
        font-size: 18px;
        font-weight: 500;
        cursor: pointer;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
        display: flex;
        align-items: center;
        justify-content: center;
        white-space: nowrap;
      }

      .add-button:hover {
        background: rgba(108, 117, 125, 0.1);
        border-color: #4caf50;
        color: #4caf50;
        transform: translate(-50%, -50%) scale(1.05);
        box-shadow: 0 6px 12px rgba(76, 175, 80, 0.15);
      }

      .add-button:active {
        transform: translate(-50%, -50%) scale(0.98);
      }

      .add-button::before {
        content: '+';
        display: inline-block;
        margin-right: 8px;
        font-size: 24px;
        font-weight: 300;
        transition: transform 0.2s;
      }

      .add-button:hover::before {
        transform: rotate(90deg);
      }

      .tile-option:hover {
        transform: scale(1.05);
        background-color: #f0f0f0;
      }

      .back-btn {
        /* 定位 */
        position: absolute;
        top: 20px;
        left: 20px;
        z-index: 100;

        /* 尺寸 */
        width: 40px;
        height: 40px;

        /* 样式 */
        background: rgba(50, 50, 50, 0.9);
        border: none;
        border-radius: 50%;
        color: white;
        font-size: 24px;
        cursor: pointer;
        box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);

        /* 交互效果 */
        transition: all 0.2s ease;
      }

      /* 鼠标悬停效果 */
      .back-btn:hover {
        background: rgba(70, 70, 70, 0.95);
        transform: scale(1.05);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
      }

      /* 点击效果 */
      .back-btn:active {
        transform: scale(0.95);
      }
      /* 移动端适配 */
      @media (max-width: 768px) {
        .back-btn {
          width: 36px;
          height: 36px;
          font-size: 20px;
          top: 12px;
          left: 12px;
        }
      }

      /* 地标相关 */
      /* 地标弹层样式 */
      .dialog {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.7);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 2000;
      }

      .dialog-content {
        position: relative;
        background: white;
        padding: 30px;
        border-radius: 10px;
        width: 80%;
        max-width: 600px;
        height: 96%;
        box-sizing: border-box;
      }
      .action-dialog .dialog-content {
        display: flex;
        flex-direction: column;
      }
      .header-container {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 20px;
      }

      .remove-btn {
        background-color: #f5f5f5;
        border: 1px solid #ddd;
        border-radius: 4px;
        padding: 6px 12px;
        cursor: pointer;
        font-size: 14px;
      }

      .remove-btn:hover {
        background-color: #e9e9e9;
      }
      .landmark-options {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 20px;
        padding: 20px;
        background-color: #ccc;
      }

      /* 地标图形样式 */
      .landmark-option {
        width: 60px;
        height: 60px;
        margin: 0 auto;
        cursor: pointer;
        transition: transform 0.3s;
      }
      .landmark-option.current {
        box-shadow: inset 0 0 13px 8px rgb(232 244 0 / 75%);
      }

      /* 石柱样式（八边形） */
      .landmark-option[class*='-pillar'] {
        clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
      }

      /* 小屋样式（三角形） */
      .landmark-option[class*='-cabin'] {
        clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
      }

      /* 颜色定义 */
      .white-cabin,
      .white-pillar {
        background-color: #fff;
      }
      .blue-cabin,
      .blue-pillar {
        background-color: #00b1e0;
      }
      .green-cabin,
      .green-pillar {
        background-color: #184925;
      }
      .black-cabin,
      .black-pillar {
        background-color: #333;
      }

      /* 地图上的地标图标 */
      .landmark {
        position: absolute;
        width: 15px;
        height: 15px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }

      .landmark[class*='-pillar'] {
        clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
      }

      .landmark[class*='-cabin'] {
        clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
      }

      /* 行动弹窗样式 */
      .action-options {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 30px;
        flex: auto;
      }
      .action-options .player-options {
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        gap: 10px;
        color: #fff;
      }
      .action-options .player-options .option-item {
        display: flex;
        align-items: center;
        flex: auto;
        padding-left: 15px;
      }
      .action-options .token-options .option-item {
        margin-bottom: 15px;
      }
      .step2-content {
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
        height: 100%;
        gap: 11%;
      }
      .step2-content h3 {
        text-align: center;
        margin: 1% auto;
      }
      .step2-content .player-options,
      .step2-content .token-options {
        display: flex;
        gap: 10px;
        height: 100px;
        align-items: center;
        width: 80%;
      }
      .step2-content .player-options .label,
      .step2-content .token-options .label {
        width: 20%;
        text-align: center;
      }

      .step2-content .option-item {
        border-radius: 4px;
        cursor: pointer;
        transition: all 0.2s;
        height: 100%;
        width: 13%;
        box-sizing: border-box;
        flex: auto;
        color: #fff;
        font-size: 24px;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .step2-content .option-item.disabled {
        opacity: 0.3;
      }
      .step3-content {
        height: 100%;
        display: flex;
        flex-direction: column;
        width: 70%;
        padding: 3%;
        box-sizing: border-box;
      }
      .step3-content h3 {
        text-align: center;
      }
      .step3-content .clue-item.selected {
        background: #f292b0;
      }

      .option-item.selected {
        box-shadow: 0px 0px 12px 0px blue;
      }

      .token-preview {
        width: 45px;
        height: 45px;
        display: inline-block;
        vertical-align: middle;
      }

      .square {
        clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
      }

      .circle {
        border-radius: 50%;
      }

      .action-info {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        display: flex;
        gap: 2px;
        width: 100%;
        height: 100%;
        align-items: center;
        flex-wrap: wrap;
        justify-content: center;
        align-content: center;
      }

      /* 不同数量token的布局 */
      .token-grid-1 {
        grid-template-columns: repeat(1, 1fr);
      }
      .token-grid-2 {
        grid-template-columns: repeat(2, 1fr);
      }
      .token-grid-3 {
        grid-template-areas:
          'top top'
          'left right';
        grid-template-columns: 1fr 1fr;
      }
      .token-grid-4 {
        grid-template-columns: repeat(2, 1fr);
        grid-template-rows: repeat(2, 1fr);
      }
      .token-grid-5 {
        grid-template-columns: repeat(3, 1fr);
        grid-template-rows: repeat(2, 1fr);
      }

      .token-mark {
        width: 10px;
        height: 10px;
        margin: 1px;
        position: relative;
      }

      /* 玩家颜色定义 */
      .player-alpha {
        background-color: var(--player-alpha);
      } /* 红色 */
      .player-beta {
        background-color: var(--player-beta);
      } /* 青色 */
      .player-gamma {
        background-color: var(--player-gamma);
      } /* 蓝色 */
      .player-delta {
        background-color: var(--player-delta);
      } /* 绿色 */
      .player-epsilon {
        background-color: var(--player-epsilon);
      }
      /* 玩家颜色类 */
      .player-alpha {
        --player-color: var(--player-alpha);
      }
      .player-beta {
        --player-color: var(--player-beta);
      }
      .player-gamma {
        --player-color: var(--player-gamma);
      }
      .player-delta {
        --player-color: var(--player-delta);
      }
      .player-epsilon {
        --player-color: var(--player-epsilon);
      }

      /* Token形状 */
      .token-mark.square {
        clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
      }
      .token-mark.circle {
        border-radius: 50%;
      }

      /* 数量布局适配 */
      .token-mark:nth-child(1).token-count-3 {
        grid-area: top;
      }
      .token-mark:nth-child(2).token-count-3 {
        grid-area: left;
      }
      .token-mark:nth-child(3).token-count-3 {
        grid-area: right;
      }

      .target-highlight {
        box-shadow: inset 0 0 8px 5px rgb(9 255 0 / 80%);
        animation: pulse-glow 1.5s ease-in-out infinite;
      }
      .target-highlight.impossible {
        animation: none; /* 取消动画 */
      }
      @keyframes pulse-glow {
        0% {
          opacity: 1;
        }
        50% {
          opacity: 0.7;
        }
        100% {
          opacity: 1;
        }
      }

      /* 线索面板 */
      /* 线索面板样式 */
      .function-btn {
        position: fixed;
        top: 20px;
        right: 20px;
        z-index: 1000;
        padding: 10px 20px;
        background: #4caf50;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }

      .clue-panel-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 2000;
      }

      .clue-panel {
        background: white;
        width: 90%;
        max-width: 600px;
        max-height: 80vh;
        padding: 20px;
        border-radius: 8px;
        position: relative;
      }

      .player-switcher {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 20px;
      }
      .player-switcher h3 {
        transition: color 0.3s;
      }
      .player-switcher h3.alpha {
        color: var(--player-alpha);
      }
      .player-switcher h3.beta {
        color: var(--player-beta);
      }
      .player-switcher h3.gamma {
        color: var(--player-gamma);
      }
      .player-switcher h3.delta {
        color: var(--player-delta);
      }
      .player-switcher h3.epsilon {
        color: var(--player-epsilon);
      }
      .remaining-clues {
        font-size: 0.8em;
        color: #666;
      }

      .player-switcher button {
        padding: 8px 12px;
        font-size: 16px;
      }
      /* 新增布局样式 */
      .panel-content {
        display: flex;
        gap: 20px;
        height: 60vh;
      }
      .clue-list {
        flex: 1;
        overflow-y: auto;
        padding-right: 10px;
      }

      .clue-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 12px;
        margin: 8px 0;
        border-radius: 8px;
        background: #f5f5f5;
        transition: all 0.2s;
        border-left: 4px solid #9de99d;
      }
      .clue-item.selected {
        background: var(--player-color);
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        transform: translateX(5px);
        color: #fff;
      }
      .clue-content {
        flex: 1;
        margin-right: 16px;
      }
      .clue-item.opposite {
        border-left-color: #ff9800;
      }

      /* 已排除线索样式 */
      .clue-item.excluded {
        opacity: 0.4;
        text-decoration: line-through;
      }

      .action-buttons {
        flex: 0 0 50%; /* 调整宽度为60% */
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
      }

      .find-intersection-btn {
        width: 100%;
        padding: 12px;
        background: #2196f3;
        color: white;
        border: none;
        border-radius: 8px;
        font-size: 14px;
        cursor: pointer;
        transition: all 0.2s;
        white-space: nowrap;
      }

      /* 已选线索样式 */
      .selected-clues-section {
        background: #f8f9fa;
        border-radius: 8px;
        padding: 15px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        flex: auto;
        display: flex;
        flex-direction: column;
        overflow: hidden;
      }

      .selected-clues-section h4 {
        margin: 0 0 12px 0;
        color: #2c3e50;
        font-size: 16px;
        border-bottom: 2px solid #e0e0e0;
        padding-bottom: 8px;
      }

      .selected-clues-list {
        display: flex;
        flex-direction: column;
        gap: 8px;
        overflow-y: auto;
        flex: auto;
      }

      .selected-clue-item {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        background: white;
        padding: 10px;
        border-radius: 6px;
        font-size: 14px;
        line-height: 1.4;
        display: flex;
        align-items: flex-start;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
        flex-shrink: 0;
      }

      .player-name {
        font-weight: 600;
        color: #4a5568;
        min-width: 70px;
      }

      .clue-desc {
        color: #718096;
        flex: 1;
        word-break: break-word;
      }

      .no-clues-hint {
        color: #a0aec0;
        font-style: italic;
        text-align: center;
        padding: 10px;
      }

      /* 推荐按钮 */
      .recommend-btn {
        top: 33%; /* 在玩家线索按钮下方 */
        background: #2196f3;
      }

      /* 推荐弹层 */
      .recommendation-list {
        margin-top: 20px;
        max-height: 60vh;
        overflow-y: auto;
      }

      .spot-item {
        display: flex;
        align-items: center;
        padding: 12px;
        margin: 8px 0;
        background: #f5f5f5;
        border-radius: 8px;
        transition: all 0.2s;
      }

      .spot-item:hover {
        transform: translateX(5px);
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
      }

      .coordinates {
        flex: 1;
        font-weight: 500;
        color: #2c3e50;
      }

      /* .token-type {
        width: 120px;
        text-align: center;
        padding: 4px 8px;
        border-radius: 4px;
        margin: 0 15px;
      }

      .token-type.square {
        background: var(--player-alpha);
        color: white;
      }

      .token-type.circle {
        background: var(--player-beta);
        color: white;
      } */

      .no-result {
        text-align: center;
        color: #666;
        padding: 20px;
      }
      /* 自动高亮提示 */
      .auto-highlight-alert {
        position: fixed;
        top: 20%;
        left: 50%;
        transform: translateX(-50%);
        background: #ffeb3b;
        padding: 15px 25px;
        border-radius: 8px;
        box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
        z-index: 3000;
        animation: pulse 2s infinite;
      }

      @keyframes pulse {
        0% {
          transform: translateX(-50%) scale(1);
        }
        50% {
          transform: translateX(-50%) scale(1.05);
        }
        100% {
          transform: translateX(-50%) scale(1);
        }
      }
      /* 日志条目样式 */
      .log-group {
        margin: 10px 0;
        border: 1px solid #ddd;
        border-radius: 8px;
        overflow: hidden;
      }
      .group-header {
        padding: 10px;
        background: #f8f9fa;
        border-bottom: 1px solid #eee;
        font-weight: bold;
      }
      .group-clues {
        padding: 10px;
        background: white;
      }
      .clue-item {
        margin: 4px 0;
        padding: 6px;
        background: #f5f5f5;
        border-radius: 4px;
      }
      .log-list {
        max-height: 60vh;
        overflow-y: auto;
        padding: 10px;
      }
      .log-item {
        padding: 10px;
        margin: 8px 0;
        background: #f5f5f5;
        border-radius: 8px;
        font-size: 14px;
      }
      .type-tag {
        display: inline-block;
        width: 24px;
        text-align: center;
        border-radius: 4px;
        margin-right: 8px;
        font-size: 12px;
        color: white;
      }

      .type-tag.circle {
        background: var(--player-beta);
      }

      .type-tag.square {
        background: var(--player-alpha);
      }

      .time {
        float: right;
        color: #666;
        font-size: 12px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div v-if="alertMessage" class="alert-warning">{{ alertMessage }}</div>
      <!-- 全屏遮罩层 -->
      <div v-if="showOverlay" class="fullscreen-overlay">
        <div class="ios-tip">
          <p>为了最佳体验，请切换为横屏<span v-if="isIOS">，点击safari分享按钮选择「添加到主屏幕」，然后从主屏幕打开</span></p>
        </div>
        <button class="btn" @click="enterFullscreen">点击开始</button>
      </div>
      <!-- 第一步配置地图 -->
      <div class="setp1-container" v-if="gameStage === 1">
        <div class="grid">
          <div v-for="(cell, index) in gridCells" :key="index" class="grid-item">
            <template v-if="cell.tile === null">
              <button class="add-button" @click="showTileSelector(index)">添加板块</button>
            </template>
            <template v-else>
              <div
                class="map-tile"
                :style="{
                    backgroundImage: `url(${cell.tile}.png)`,
                    transform: cell.reversed ? 'rotate(180deg)' : 'none'
                }"
              ></div>
              <div class="controls">
                <button @click="rotateTile(index)">↻ 旋转</button>
                <button @click="removeTile(index)">× 删除</button>
              </div>
            </template>
          </div>
        </div>
        <div class="expert-toggle">
          <label> <input type="checkbox" v-model="expertMode" /> 专家模式 </label>
        </div>
        <button class="act-btn" :disabled="!allTilesSet" @click="gameStage = 2">🚀 选择玩家</button>
        <!-- 地图板块选择弹层 -->
        <div v-if="showSelector" class="selector-overlay" @click.self="closeSelector">
          <div class="selector-content">
            <button class="close-btn" @click="closeSelector">×</button>
            <h3>选择地图板（剩余 {{ availableTiles.length }} 个）</h3>
            <div class="tile-options">
              <div v-for="tile in availableTiles" :key="tile" class="tile-option" :style="{ backgroundImage: `url(${tile}.png)` }" @click="selectTile(tile)">
                <div class="tile-number">{{ tile }}</div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- 第2步：选择玩家 -->
      <div class="step2-content" v-else-if="gameStage === 2">
        <button class="back-btn" @click="handleBack">←</button>
        <h3>选择玩家</h3>
        <!-- 选择自己的玩家 -->
        <div class="player-options">
          <h4 class="label">选择自己</h4>
          <div v-for="player in players" :key="player" class="option-item" :class="[`player-${player}`, {selected: player == selfPlayer}]" @click="selectSelfPlayer(player)">
            {{ player }}
          </div>
        </div>
        <!-- 选择其他玩家 -->
        <div v-if="selfPlayer" class="player-options">
          <h4 class="label">选择其他玩家<br />（至少选择两个）</h4>
          <div
            v-for="player in players"
            :key="player"
            class="option-item"
            :class="[`player-${player}`, {selected: otherPlayers.includes(player), disabled:player == selfPlayer}]"
            @click="toggleOtherPlayer(player)"
          >
            {{ player }}
          </div>
        </div>
        <button class="act-btn start-btn" :disabled="otherPlayers.length < 2" @click="gameStage = 3">🚀 选择线索</button>
      </div>

      <!-- 第3步，选择线索 -->
      <div class="step3-content" v-else-if="gameStage === 3">
        <button class="back-btn" @click="handleBack">←</button>
        <h3>选择线索</h3>
        <div class="clue-list">
          <div v-for="(clue, key) in clues[selfPlayer].normal" :key="key" :class="['clue-item', {selected: selfClue === key}]" @click="selectSelfClue(key)">
            {{ clue.desc }}
          </div>
          <!-- 专家模式反向线索 -->
          <template v-if="expertMode">
            <div
              v-for="(clue, key) in clues[selfPlayer].opposite"
              :key="'opposite-'+key"
              :class="['clue-item', 'opposite',{selected: selfClue === 'opposite-' + key}]"
              @click="selectSelfClue('opposite-' + key)"
            >
              【不在】{{ clue.desc }}
            </div>
          </template>
        </div>
        <button class="act-btn start-btn" :disabled="!selfClue" @click="genMapBtnHandler">🚀 生成地图</button>
      </div>

      <!-- 第4步，生成地图 ，第5步 开始游戏-->
      <div class="map-container" v-else-if="gameStage > 3">
        <button class="back-btn" @click="handleBack">←</button>
        <!-- 地图 -->
        <div class="map-row" v-for="(row, rowIndex) in gameMap" :key="rowIndex">
          <div class="hex-container" v-for="(cell, colIndex) in row" :key="colIndex">
            <div
              class="hexagon"
              :class="[
                cell.terrain,
                { 'target-highlight': isHighlighted(rowIndex, colIndex), impossible: cell.tokens?.some((t) => t.type === 'square') },
              ]"
              @click="hexgonClickHandler(rowIndex, colIndex)"
              :style="{ zIndex: 9 - rowIndex }"
            >
              <div class="fox-icon" v-if="cell.animal == 'fox'" title="狐狸领地">🦊</div>
              <div class="bear-icon" v-if="cell.animal == 'bear'" title="黑熊领地">🐻</div>
              <div v-if="cell.landmark" class="landmark" :class="cell.landmark"></div>

              <div class="action-info" v-if="cell.tokens && cell.tokens.length">
                <div
                  v-for="(token, index) in cell.tokens"
                  :key="index"
                  class="token-mark"
                  :class="[
                    `player-${token.player.toLowerCase()}`,
                    token.type,
                    `token-count-${cell.tokens.length}`
                  ]"
                ></div>
              </div>
            </div>
          </div>
        </div>
        <!-- 设置地标 -->
        <button v-if="gameStage == 4" class="act-btn start-btn" :disabled="!allLandmarksSet" @click="gameStage = 5">
          {{ allLandmarksSet ? '🚀 开始游戏' : '请先设置完所有地标' }}
        </button>

        <button v-if="gameStage == 5" class="function-btn" @click="showCluePanel = true">玩家线索</button>
        <button v-if="gameStage == 5" class="function-btn recommend-btn" @click="showRecommendation" style="top: 19%">📍 推荐放置点</button>
        <button v-if="gameStage == 5" class="function-btn" @click="showExcludeLog = true" style="top: 33%">查看日志</button>
        <button v-if="gameStage == 5" class="function-btn" @click="clearHighlight" style="top: 47%">清除高亮</button>

        <!-- 地标选择弹层 -->
        <div v-if="showLandmarkDialog" class="landmark-dialog dialog" @click.self="closeLandmarkDialog">
          <div class="dialog-content">
            <button class="close-btn" @click="closeLandmarkDialog">×</button>
            <div class="header-container">
              <h3>选择地标 (剩余 {{ availableLandmarks.length }} 个)</h3>
              <button class="remove-btn" @click="removeLandmark">移除该点地标</button>
            </div>

            <div class="landmark-options">
              <div
                v-if="gameMap[selectedHex.row][selectedHex.col].landmark"
                :key="gameMap[selectedHex.row][selectedHex.col].landmark"
                class="landmark-option current"
                :class="gameMap[selectedHex.row][selectedHex.col].landmark"
                @click.stop="selectLandmark(gameMap[selectedHex.row][selectedHex.col].landmark)"
              ></div>
              <div v-for="landmark in availableLandmarks" :key="landmark" class="landmark-option" :class="landmark" @click.stop="selectLandmark(landmark)"></div>
            </div>
          </div>
        </div>

        <!-- 行动选择层 -->
        <div v-if="showActionDialog" class="action-dialog dialog" @click.self="closeActionDialog">
          <div class="dialog-content">
            <button class="close-btn" @click="closeActionDialog">×</button>
            <h3>选择行动</h3>
            <div class="action-options">
              <div class="player-options">
                <div
                  v-for="player in players"
                  :key="player"
                  class="option-item"
                  :class="[
                    { selected: selectedAction.player === player },
                    `player-${player}`
                  ]"
                  @click="selectedAction.player = player"
                >
                  {{ player }}
                </div>
              </div>
              <div class="token-options" v-if="selectedAction.player">
                <div
                  v-for="token in tokens"
                  :key="token.type"
                  class="option-item"
                  :class="{ selected: selectedAction.token === token.type }"
                  @click="selectedAction.token = token.type"
                >
                  <div class="token-preview" :class="[token.class, `player-${selectedAction.player}`]"></div>
                  {{ token.label }}
                </div>
              </div>
            </div>
            <button class="act-btn" :disabled="!isActionValid" @click="saveAction">确定</button>
          </div>
        </div>

        <!-- 线索面板 -->
        <div v-if="showCluePanel" class="clue-panel-overlay" @click.self="showCluePanel = false">
          <button class="close-btn" @click="showCluePanel = false">×</button>
          <div class="clue-panel">
            <!-- 玩家切换 -->
            <div class="player-switcher">
              <button @click="currentPlayerIndex = (currentPlayerIndex - 1 + otherPlayersCount) % otherPlayersCount">◀</button>
              <h3 :class="currentPlayer">
                {{ currentPlayer }}
                <span class="remaining-clues">(剩余线索: {{ remainingCluesCount[currentPlayer] }})</span>
                <!-- <button class="ask-points-btn" @click="showAskPoints(currentPlayer)">推荐询问点</button> -->
              </h3>
              <button @click="currentPlayerIndex = (currentPlayerIndex + 1) % otherPlayersCount">▶</button>
            </div>
            <!-- 布局容器 -->
            <div class="panel-content">
              <!-- 线索列表 -->
              <div class="clue-list">
                <!-- 普通线索 -->
                <label
                  v-for="(clue, key) in currentClues.normal"
                  :key="key"
                  :class="['clue-item', `player-${currentPlayer}`, { excluded: clue.exclude,selected: selectedClues[currentPlayer] === key }]"
                  @click="toggleClue(currentPlayer, key, $event)"
                >
                  <div class="clue-content">
                    {{ clue.desc }}
                    <span v-if="clue.exclude">(已排除)</span>
                    <span> 交集：{{ (cluesIntersectionCount[currentPlayer]?.normal[key]) || 0 }}</span>
                  </div>
                  <input type="hidden" :name="currentPlayer" :value="key" />
                </label>

                <!-- 专家模式反向线索 -->
                <template v-if="expertMode">
                  <label
                    v-for="(clue, key) in currentClues.opposite"
                    :key="'opposite-'+key"
                    :class="['clue-item', 'opposite', `player-${currentPlayer}`, { excluded: clue.exclude, selected: selectedClues[currentPlayer] === 'opposite-'+key }]"
                    @click="toggleClue(currentPlayer, 'opposite-'+key, $event)"
                  >
                    <div class="clue-content">
                      [不在] {{ clue.desc }}
                      <span v-if="clue.exclude">(已排除)</span>
                      <span> 交集：{{ (cluesIntersectionCount?.[currentPlayer]?.opposite[key]) || 0 }}</span>
                    </div>
                    <input type="hidden" :name="currentPlayer" :value="key" />
                  </label>
                </template>
              </div>

              <!-- 查看交集按钮 -->
              <div class="action-buttons">
                <button v-if="hasSelectedClues" class="find-intersection-btn" @click="showCluesIntersection">🔍 查看线索交集</button>
                <!-- 已选线索列表 -->
                <div class="selected-clues-section">
                  <h4>已选线索</h4>
                  <div v-if="hasSelectedClues" class="selected-clues-list">
                    <div v-for="(clueKey, player) in filteredSelectedClues" :key="player" class="selected-clue-item">
                      <span class="player-name">{{ player }}：</span>
                      <span class="clue-desc">{{ getClueDescription(player, clueKey) }}</span>
                    </div>
                  </div>
                  <div v-else class="no-clues-hint">暂无已选线索</div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <!-- 推荐放置点弹层 -->
        <div v-if="showRecommendationDialog" class="dialog" @click.self="showRecommendationDialog = false">
          <div class="dialog-content">
            <button class="close-btn" @click="showRecommendationDialog = false">×</button>
            <h3>推荐放置点（最小暴露值：{{ recommendationExposure }}）</h3>
            <div class="recommendation-list">
              <div v-if="recommendedSpots.length === 0" class="no-result">没有找到最佳放置点</div>
              <div v-for="(spot, index) in recommendedSpots" :key="index" class="spot-item">
                <span class="coordinates">坐标：{{ formatCoordinates(spot.r, spot.c) }}</span>
                <!-- <span class="token-type" :class="spot.type"> {{ spot.type === 'square' ? '不可能（方）' : '可能（圆）' }} </span> -->
                <button @click="highlightSpot(spot)">高亮</button>
              </div>
            </div>
          </div>
        </div>
        <!-- 推荐询问点 -->
        <div v-if="showAskPointsDialog" class="dialog" @click.self="showAskPointsDialog = false">
          <div class="dialog-content">
            <button class="close-btn" @click="showAskPointsDialog = false">×</button>
            <h3>推荐询问点（可能最大排除值：{{ maxExpose }}）</h3>
            <div class="recommendation-list">
              <div v-if="recommendedAskPoints.length === 0" class="no-result">没有找到最佳询问点</div>
              <div v-for="(spot, index) in recommendedAskPoints" :key="index" class="spot-item">
                <span class="coordinates">坐标：{{ formatCoordinates(spot.r, spot.c) }}，最少排除{{spot.min}}</span>
                <button @click="highlightSpot(spot)">高亮</button>
              </div>
            </div>
          </div>
        </div>

        <!-- 排除日志弹层 -->
        <div v-if="showExcludeLog" class="clue-panel-overlay" @click.self="showExcludeLog = false">
          <div class="clue-panel">
            <button class="close-btn" @click="showExcludeLog = false">×</button>
            <h3>排除日志（{{ groupedLogs.length }}组）</h3>
            <div class="log-list">
              <div v-for="group in groupedLogs" :key="group.key" class="log-group">
                <div class="group-header">玩家 {{ group.player }} 在{{ group.row +1 }}行{{ group.col +1 }}列 放置 （排除{{ group.count }}条线索）</div>
                <div class="group-clues">
                  <div v-for="(log, index) in group.logs" :key="index" class="clue-item">
                    <span class="type-tag" :class="log.type">{{ log.type === 'circle' ? '圆' : '方' }}</span>
                    {{ log.isOpposite ? '不在' : '' }}{{ log.clueDesc }}
                    <span class="time">{{ formatTime(log.timestamp) }}</span>
                  </div>
                </div>
              </div>
              <div v-if="!groupedLogs.length" class="no-result">暂无排除记录</div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <script>
      window.addEventListener('load', function () {
        setTimeout(function () {
          window.scrollTo(0, 1);
        }, 100);
      });

      // 6个地图板块
      const mapTiles = {
        1: [
          [
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: null },
          ],
          [
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: null },
          ],
          [
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: 'bear' },
            { terrain: 'desert', animal: 'bear' },
            { terrain: 'forest', animal: 'bear' },
          ],
        ],
        2: [
          [
            { terrain: 'marsh', animal: 'fox' },
            { terrain: 'forest', animal: 'fox' },
            { terrain: 'forest', animal: 'fox' },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: null },
          ],
          [
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: null },
          ],
          [
            { terrain: 'marsh', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'desert', animal: null },
          ],
        ],
        3: [
          [
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'ocean', animal: null },
          ],
          [
            { terrain: 'marsh', animal: 'fox' },
            { terrain: 'marsh', animal: 'fox' },
            { terrain: 'forest', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
          ],
          [
            { terrain: 'mountain', animal: 'fox' },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
          ],
        ],

        4: [
          [
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
          ],
          [
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: 'fox' },
          ],
          [
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: 'fox' },
          ],
        ],
        5: [
          [
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: null },
          ],
          [
            { terrain: 'marsh', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'mountain', animal: null },
            { terrain: 'mountain', animal: 'bear' },
          ],
          [
            { terrain: 'desert', animal: null },
            { terrain: 'desert', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: 'bear' },
            { terrain: 'ocean', animal: 'bear' },
          ],
        ],
        6: [
          [
            { terrain: 'desert', animal: 'bear' },
            { terrain: 'desert', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'forest', animal: null },
          ],
          [
            { terrain: 'mountain', animal: 'bear' },
            { terrain: 'mountain', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'marsh', animal: null },
            { terrain: 'forest', animal: null },
            { terrain: 'forest', animal: null },
          ],
          [
            { terrain: 'mountain', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'ocean', animal: null },
            { terrain: 'forest', animal: null },
          ],
        ],
      };
      // 所有线索
      const BASE_CLUE = {
        forest_ocean: {
          desc: '森林或海洋之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'forest' }, { terrain: 'ocean' }], // 对应的目标
          distance: 0, // 距离
        },
        forest_desert: {
          desc: '森林或沙漠之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'forest' }, { terrain: 'desert' }], // 对应的目标
          distance: 0, // 距离
        },
        forest_mountain: {
          desc: '森林或山地之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'forest' }, { terrain: 'mountain' }], // 对应的目标
          distance: 0, // 距离
        },
        forest_marsh: {
          desc: '森林或沼泽之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'forest' }, { terrain: 'marsh' }], // 对应的目标
          distance: 0, // 距离
        },
        ocean_desert: {
          desc: '海洋或沙漠之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'ocean' }, { terrain: 'desert' }], // 对应的目标
          distance: 0, // 距离
        },
        ocean_mountain: {
          desc: '海洋或山地之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'ocean' }, { terrain: 'mountain' }], // 对应的目标
          distance: 0, // 距离
        },
        ocean_marsh: {
          desc: '海洋或沼泽之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'ocean' }, { terrain: 'marsh' }], // 对应的目标
          distance: 0, // 距离
        },
        desert_mountain: {
          desc: '沙漠或山地之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'desert' }, { terrain: 'mountain' }], // 对应的目标
          distance: 0, // 距离
        },
        desert_marsh: {
          desc: '沙漠或沼泽之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'desert' }, { terrain: 'marsh' }], // 对应的目标
          distance: 0, // 距离
        },
        mountain_marsh: {
          desc: '山地或沼泽之中',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'mountain' }, { terrain: 'marsh' }], // 对应的目标
          distance: 0, // 距离
        },

        forest_1: {
          desc: '森林1格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'forest' }], // 对应的目标
          distance: 1, // 距离
        },
        ocean_1: {
          desc: '海洋1格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'ocean' }], // 对应的目标
          distance: 1, // 距离
        },
        desert_1: {
          desc: '沙漠1格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'desert' }], // 对应的目标
          distance: 1, // 距离
        },
        mountain_1: {
          desc: '山地1格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'mountain' }], // 对应的目标
          distance: 1, // 距离
        },
        marsh_1: {
          desc: '沼泽1格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ terrain: 'marsh' }], // 对应的目标
          distance: 1, // 距离
        },
        animal_1: {
          desc: '任意动物1格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ animal: 'fox' }, { animal: 'bear' }], // 对应的目标
          distance: 1, // 距离
        },

        pillar_2: {
          desc: '石柱2格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ landmark: 'white-pillar' }, { landmark: 'blue-pillar' }, { landmark: 'green-pillar' }], // 对应的目标
          distance: 2, // 距离
        },
        cabin_2: {
          desc: '小屋2格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ landmark: 'white-cabin' }, { landmark: 'blue-cabin' }, { landmark: 'green-cabin' }], // 对应的目标
          distance: 2, // 距离
        },
        fox_2: {
          desc: '狐狸2格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ animal: 'fox' }], // 对应的目标
          distance: 2, // 距离
        },
        bear_2: {
          desc: '黑熊2格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ animal: 'bear' }], // 对应的目标
          distance: 2, // 距离
        },

        white_3: {
          desc: '白色地标3格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ landmark: 'white-pillar' }, { landmark: 'white-cabin' }], // 对应的目标
          distance: 3, // 距离
        },

        blue_3: {
          desc: '蓝色地标3格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ landmark: 'blue-pillar' }, { landmark: 'blue-cabin' }], // 对应的目标
          distance: 3, // 距离
        },
        green_3: {
          desc: '绿色地标3格距离之内',
          exclude: false, // 是否已排除该线索
          targets: [{ landmark: 'green-pillar' }, { landmark: 'green-cabin' }], // 对应的目标
          distance: 3, // 距离
        },
      };
      const EXPERT_CLUE = {
        black_3: {
          desc: '黑色地标3格距离之内',
          exclude: false,
          targets: [{ landmark: 'black-pillar' }, { landmark: 'black-cabin' }],
          distance: 3,
        },
      };

      new Vue({
        el: '#app',
        data: {
          viewport: {
            width: 0,
            height: 0,
          },
          showOverlay: true,
          isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream,
          alertMessage: null,
          gameStage: 1, // 游戏阶段
          gridCells: Array(6)
            .fill()
            .map(() => ({
              tile: null,
              reversed: false,
            })),
          allTiles: [1, 2, 3, 4, 5, 6],
          selectedCellIndex: null,
          showSelector: false,

          selfPlayer: null, // 自己的玩家
          otherPlayers: [], // 其他玩家
          selfClue: null, // 我的线索
          // 地图数据
          gameMap: [],

          // 地标弹层相关
          showLandmarkDialog: false,
          selectedHex: null,
          allLandmarks: [
            'white-pillar',
            'blue-pillar',
            'green-pillar',
            'white-cabin',
            'blue-cabin',
            'green-cabin',
            'black-pillar', // 专家模式专用
            'black-cabin', // 专家模式专用
          ],
          expertMode: false, // 专家模式开关
          // 行动层相关
          showActionDialog: false,
          players: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
          tokens: [
            { type: 'square', label: '不可能（方）', class: 'square' },
            { type: 'circle', label: '可能（圆）', class: 'circle' },
          ],
          selectedAction: {
            player: '',
            token: '',
          },
          // 所有玩家的线索
          clues: {}, // 初始化为空对象
          showCluePanel: false,
          currentPlayerIndex: 0, // 当前显示玩家索引
          selectedClues: {
            alpha: null,
            beta: null,
            gamma: null,
            delta: null,
            epsilon: null,
          },
          showRecommendationDialog: false,
          showAskPointsDialog: false,
          highlightedCells: [],
          recommendedSpots: [],
          recommendationExposure: 0,
          showExcludeLog: false,
          excludeLog: [], // 排除日志
          cluesIntersectionCount: {}, // 线索交集计数
        },
        created() {
          // 初始化玩家的基本线索
          this.players.forEach((player) => {
            (this.clues[player] || (this.clues[player] = {})).normal = this.deepCloneCLUE(false);
            this.clues[player].opposite = this.deepCloneCLUE(false);
          });
        },
        computed: {
          availableTiles() {
            const usedTiles = this.gridCells.map((cell) => cell.tile).filter((tile) => tile !== null);
            return this.allTiles.filter((tile) => !usedTiles.includes(tile));
          },
          allTilesSet() {
            return this.gridCells.every((cell) => cell.tile !== null);
          },
          // 所有待选地标
          availableLandmarks() {
            return this.allLandmarks.filter((mark) => {
              const isAvailable = !this.gameMap.some((row) => row.some((cell) => cell.landmark === mark));
              const isBlack = mark.includes('black');
              return isAvailable && (!isBlack || (isBlack && this.expertMode));
            });
          },
          // 是否所有地标都已设置
          allLandmarksSet() {
            return this.availableLandmarks.length === 0;
          },
          isActionValid() {
            return this.selectedAction.player && this.selectedAction.token;
          },
          currentPlayer() {
            // return this.players[this.currentPlayerIndex];
            return this.otherPlayers[this.currentPlayerIndex];
          },
          // 当前显示玩家线索
          currentClues() {
            return this.clues[this.currentPlayer];
          },

          // 优化性能，使用Set存储坐标
          //   highlightedCellsSet() {
          //     return new Set(this.highlightedCells.map(({ r, c }) => `${r},${c}`));
          //   },
          otherPlayersCount() {
            return this.otherPlayers.length;
          },
          filteredSelectedClues() {
            let _clues = Object.entries(this.selectedClues)
              .filter(([_, value]) => value !== null)
              .reduce(
                (acc, [key, value]) => {
                  acc[key] = value;
                  return acc;
                },
                { [this.selfPlayer]: this.selfClue }
              );
            return _clues;
          },
          // 是否有选中线索
          hasSelectedClues() {
            return Object.values(this.filteredSelectedClues).some((v) => v !== null);
          },
          // 剩余线索
          remainingClues() {
            const _remainingClues = {};
            for (const player of this.otherPlayers) {
              // 提取 normal 中的非 exclude 项
              const normalClues = Object.entries(this.clues[player].normal)
                .filter(([_, clue]) => !clue.exclude)
                .reduce((acc, [key, clue]) => ({ ...acc, [key]: clue }), {});

              // 提取 opposite 中的非 exclude 项（如果启用专家模式）
              const oppositeClues = this.expertMode
                ? Object.entries(this.clues[player].opposite)
                    .filter(([_, clue]) => !clue.exclude)
                    .reduce((acc, [key, clue]) => ({ ...acc, [key]: clue }), {})
                : {};

              // 将 normal 和 opposite 的线索分别存储
              _remainingClues[player] = {
                normal: normalClues,
                opposite: oppositeClues,
              };
            }

            return _remainingClues;
            //   const clues = [...Object.values(this.clues[player].normal), ...(this.expertMode ? Object.values(this.clues[player].opposite) : [])];
            //   return clues.filter((c) => !c.exclude).length;
          },
          // 其它玩家的剩余线索数
          remainingCluesCount() {
            const counts = {};
            for (const player in this.remainingClues) {
              const { normal, opposite } = this.remainingClues[player];
              // 计算 normal 和 opposite 的属性个数
              const normalCount = Object.keys(normal).length;
              const oppositeCount = Object.keys(opposite).length;
              // 总属性个数
              counts[player] = normalCount + oppositeCount;
            }
            return counts;
          },

          sortedExcludeLog() {
            return [...this.excludeLog].sort((a, b) => b.timestamp - a.timestamp);
          },
          groupedLogs() {
            const groups = {};
            this.excludeLog.forEach((log) => {
              const key = `${log.row}-${log.col}-${log.player}`;
              if (!groups[key]) {
                groups[key] = {
                  key,
                  row: log.row,
                  col: log.col,
                  player: log.player,
                  count: 0,
                  logs: [],
                };
              }
              groups[key].count++;
              groups[key].logs.push(log);
            });

            // 按时间倒序排列每个组内的日志
            return Object.values(groups)
              .map((group) => ({
                ...group,
                logs: group.logs.sort((a, b) => b.timestamp - a.timestamp),
              }))
              .sort((a, b) => b.logs[0].timestamp - a.logs[0].timestamp);
          },
        },
        watch: {
          // 监听 remainingClues 的变化
          remainingClues: {
            handler: async function (newVal, oldVal) {
              if (newVal !== oldVal) {
                await this.updateCluesIntersectionCount();
              }
            },
            deep: true, // 深度监听
            immediate: true, // 立即执行一次
          },
          // 如果其他依赖数据（如 selfClue、expertMode 等）发生变化，也可以添加相应的监听器
          selfClue: {
            handler: async function () {
              await this.updateCluesIntersectionCount();
            },
            immediate: true,
          },
          expertMode: {
            handler: async function () {
              await this.updateCluesIntersectionCount();
            },
            immediate: true,
          },
        },
        mounted() {
          // 监听 ESC 退出全屏事件（可选）
          document.addEventListener('fullscreenchange', () => {
            if (!document.fullscreenElement) {
              this.showOverlay = true;
            }
          });
        },
        methods: {
          async updateCluesIntersectionCount() {
            try {
              // 检查是否所有依赖数据都已初始化
              if (!this.selfClue || !this.clues || !this.clues[this.selfPlayer]) {
                return;
              }

              const isOpposite = this.selfClue.startsWith('opposite-');
              const clue = this.clues[this.selfPlayer][isOpposite ? 'opposite' : 'normal'][this.selfClue.replace('opposite-', '')];
              const selfCoords = await this.getClueCoordinates(clue, isOpposite);
              const set1 = new Set(selfCoords.map(({ r, c }) => `${r},${c}`));

              const _cluesIntersectionCount = {};
              for (const player in this.remainingClues) {
                _cluesIntersectionCount[player] = { normal: {}, opposite: {} };
                for (const key in this.remainingClues[player].normal) {
                  const clue = this.remainingClues[player].normal[key];
                  const coords2 = await this.getClueCoordinates(clue, false);
                  const set2 = coords2.map(({ r, c }) => `${r},${c}`);
                  const intersection = new Set([...set2].filter((x) => set1.has(x)));
                  _cluesIntersectionCount[player].normal[key] = intersection.size;
                }
                if (this.expertMode) {
                  for (const key in this.remainingClues[player].opposite) {
                    const clue = this.remainingClues[player].opposite[key];
                    const coords2 = await this.getClueCoordinates(clue, false);
                    const set2 = coords2.map(({ r, c }) => `${r},${c}`);
                    const intersection = new Set([...set2].filter((x) => set1.has(x)));
                    _cluesIntersectionCount[player].opposite[key] = intersection.size;
                  }
                }
              }
              // 更新响应式数据属性
              this.cluesIntersectionCount = _cluesIntersectionCount;
            } catch (error) {
              console.error('Error updating clues intersection count:', error);
            }
          },
          formatTime(timestamp) {
            const d = new Date(timestamp);
            return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}`;
          },
          enterFullscreen() {
            // 关闭遮罩层
            this.showOverlay = false;

            // 触发全屏
            const element = document.documentElement;
            if (element.requestFullscreen) {
              element.requestFullscreen();
            } else if (element.webkitRequestFullscreen) {
              // Safari 专用
              element.webkitRequestFullscreen();
            } else if (element.mozRequestFullScreen) {
              // Firefox
              element.mozRequestFullScreen();
            } else if (element.msRequestFullscreen) {
              // IE/Edge
              element.msRequestFullscreen();
            }
          },
          showCustomAlert(msg) {
            this.alertMessage = msg;
            setTimeout(() => (this.alertMessage = null), 3000);
          },
          // 深拷贝方法支持专家模式
          deepCloneCLUE(isExpert) {
            const base = JSON.parse(JSON.stringify(BASE_CLUE));
            if (isExpert) {
              Object.assign(base, JSON.parse(JSON.stringify(EXPERT_CLUE)));
              base.pillar_2.targets.push({ landmark: 'black-pillar' });
              base.cabin_2.targets.push({ landmark: 'black-cabin' });
            }
            return base;
          },
          showTileSelector(index) {
            this.selectedCellIndex = index;
            this.showSelector = true;
          },
          selectTile(tile) {
            this.gridCells[this.selectedCellIndex].tile = tile;
            this.closeSelector();
          },
          rotateTile(index) {
            this.gridCells[index].reversed = !this.gridCells[index].reversed;
          },
          removeTile(index) {
            this.gridCells[index].tile = null;
            this.gridCells[index].reversed = false;
          },
          closeSelector() {
            this.showSelector = false;
          },
          // 地图生成方法
          reverseTile(tileData) {
            // 创建深拷贝避免污染原始数据
            const cloned = JSON.parse(JSON.stringify(tileData));
            // 执行双重反转
            return cloned
              .reverse() // 1. 反转行顺序
              .map((row) => row.reverse()); // 2. 反转每行元素
          },

          generateGameMap(mapSetting) {
            const positionMap = [
              { startRow: 0, startCol: 0 },
              { startRow: 0, startCol: 6 },
              { startRow: 3, startCol: 0 },
              { startRow: 3, startCol: 6 },
              { startRow: 6, startCol: 0 },
              { startRow: 6, startCol: 6 },
            ];

            const finalMap = Array.from({ length: 9 }, () => Array.from({ length: 12 }, () => null));

            mapSetting.forEach((setting, index) => {
              let tileData = JSON.parse(JSON.stringify(mapTiles[setting.tile]));
              // 应用新的反转逻辑
              if (setting.reversed) {
                tileData = this.reverseTile(tileData);
              }

              const { startRow, startCol } = positionMap[index];
              for (let r = 0; r < 3; r++) {
                for (let c = 0; c < 6; c++) {
                  finalMap[startRow + r][startCol + c] = tileData[r][c];
                }
              }
            });

            return finalMap;
          },

          // “生成地图”按钮处理器
          genMapBtnHandler() {
            // 初始化地图
            const mapSetting = this.gridCells.map((cell) => ({
              tile: cell.tile,
              reversed: cell.reversed,
            }));
            this.gameMap = this.generateGameMap(mapSetting);
            // 初始化线索
            this.clues = this.players.reduce((clues, player) => {
              clues[player] = {
                normal: this.deepCloneCLUE(this.expertMode),
                opposite: this.deepCloneCLUE(this.expertMode),
              };
              if(player !== this.selfPlayer) {
                clues[player][this.selfClue.startsWith('opposite') ? 'opposite' : 'normal'][this.selfClue.replace('opposite-', '')].exclude = true;
              }
              return clues;
            }, {});
            this.clearHighlight();
            this.gameStage = 4;
          },
          handleBack() {
            this.gameStage = this.gameStage > 3 ? 3 : this.gameStage - 1;
          },
          selectSelfPlayer(player) {
            this.selfPlayer = player;
            if (this.otherPlayers.includes(player)) {
              this.otherPlayers = this.otherPlayers.filter((p) => p !== player);
            }
          },
          toggleOtherPlayer(player) {
            if (player == this.selfPlayer) return;
            if (this.otherPlayers.includes(player)) {
              this.otherPlayers = this.otherPlayers.filter((p) => p !== player);
            } else {
              this.otherPlayers.push(player);
            }
          },
          selectSelfClue(clueKey) {
            this.selfClue = clueKey;
          },
          // 点击地块处理
          hexgonClickHandler(row, col) {
            if (this.gameStage === 4) {
              this.openLandmarkDialog(row, col);
            } else if (this.gameStage === 5) {
              this.openActionDialog(row, col);
            }
          },
          // 地标相关
          openLandmarkDialog(row, col) {
            this.selectedHex = { row, col };
            this.showLandmarkDialog = true;
          },
          closeLandmarkDialog() {
            this.showLandmarkDialog = false;
            this.selectedHex = null;
          },
          // 选择地标
          selectLandmark(landmark) {
            const { row, col } = this.selectedHex;
            const cell = this.gameMap[row][col];

            // 使用 Vue 的响应式方法操作对象属性
            if (cell.landmark === landmark) {
              this.$delete(cell, 'landmark'); // 响应式删除属性
            } else {
              this.$set(cell, 'landmark', landmark); // 响应式设置属性
            }

            // 强制刷新数组引用（关键修改）
            this.gameMap.splice(row, 1, [...this.gameMap[row]]);

            this.closeLandmarkDialog();
          },
          removeLandmark(landmark) {
            const { row, col } = this.selectedHex;
            Vue.delete(this.gameMap[row][col], 'landmark');
          },

          // 打开行动弹窗
          openActionDialog(row, col) {
            const hasSquare = this.gameMap[row][col].tokens?.some((t) => t.type === 'square');
            if (hasSquare) {
              alert('该点为不可能');
              return;
            }
            this.selectedHex = { row, col };
            const cell = this.gameMap[row][col];
            this.selectedAction = {
              player: cell.player || '',
              token: cell.token || '',
            };
            this.showActionDialog = true;
          },
          // 保存行动数据
          async saveAction() {
            const { row, col } = this.selectedHex;
            const newToken = {
              player: this.selectedAction.player,
              type: this.selectedAction.token,
            };

            // 检查是否已存在方形token
            const hasSelf = this.gameMap[row][col].tokens?.some((t) => t.player === newToken.player);
            if (hasSelf) {
              alert(`该点已有玩家${newToken.player}的token`);
              return;
            }

            this.$set(this.gameMap[row], col, {
              ...this.gameMap[row][col],
              tokens: [...(this.gameMap[row][col].tokens || []), newToken],
            });
            // 排除玩家线索
            this.excludeClues(row, col, newToken);
            // 自动高亮交叉点
            await this.autoHighlightIntersection();
            this.closeActionDialog();
          },

          // 排除指定玩家的不可能的线索
          excludeClues(row, col, { player, type }) {
            // 收集被排除的线索
            const excludedClues = new Set();

            const processClues = (isOpposite) => {
              const clues = isOpposite ? this.clues[player].opposite : this.clues[player].normal;

              Object.entries(clues).forEach(([key, clue]) => {
                if (clue.exclude) return;

                const hexagons = this.getHexagons(row, col, clue.distance);
                const found = hexagons.some((hex) => clue.targets.some((target) => this.checkCondition({ r: hex.row, c: hex.col }, target)));

                // 判断是否需要排除
                let shouldExclude = false;
                if (isOpposite) {
                  shouldExclude = (found && type === 'circle') || (!found && type === 'square');
                } else {
                  shouldExclude = (found && type === 'square') || (!found && type === 'circle');
                }

                if (shouldExclude) {
                  clue.exclude = true;
                  // 记录排除的线索（添加前缀区分类型）
                  excludedClues.add(`${isOpposite ? 'opposite-' : ''}${key}`);
                  // 记录排除日志
                  this.excludeLog.push({
                    row: row,
                    col: col,
                    player: player,
                    type: type,
                    isOpposite: isOpposite,
                    clueDesc: clue.desc,
                    timestamp: new Date(),
                  });
                }
              });
            };

            // 处理普通线索
            processClues(false);
            // 处理反向线索（专家模式）
            if (this.expertMode) processClues(true);

            // 检查自己线索是否被排除
            if (player === this.selfPlayer) {
              if (excludedClues.has(this.selfClue)) {
                this.showCustomAlert('⚠️ 警告：自己的线索被排除了！');
              }
            } else {
              // 自动移除已选择的被排除线索
              Object.keys(this.selectedClues).forEach((p) => {
                const selectedKey = this.selectedClues[p];
                if (excludedClues.has(selectedKey)) {
                  this.selectedClues[p] = null;
                  this.showCustomAlert(`玩家 ${p} 的已选线索被排除，已自动取消选择`);
                }
              });
            }
          },

          // 获取到点row,col距离为n的所有点
          getHexagons(row, col, n) {
            const directions = {
              odd: [
                [0, -1], // 左上
                [0, +1], // 右上
                [-1, 0], // 正上
                [+1, 0], // 正下
                [+1, -1], // 左下
                [+1, +1], // 右下
              ],
              even: [
                [-1, -1], // 左上
                [-1, +1], // 右上
                [-1, 0], // 正上
                [+1, 0], // 正下
                [0, -1], // 左下
                [0, +1], // 右下
              ],
            };

            const visited = new Set();
            const queue = [[row, col, 0]];
            const result = [];

            while (queue.length > 0) {
              const [r, c, d] = queue.shift();
              const key = `${r},${c}`;

              if (d > n || visited.has(key)) continue;

              visited.add(key);
              result.push({ row: r, col: c });

              if (d < n) {
                const dirType = c % 2 === 1 ? 'odd' : 'even';
                directions[dirType].forEach(([dr, dc]) => {
                  const nr = r + dr;
                  const nc = c + dc;
                  if (nr >= 0 && nr < 9 && nc >= 0 && nc < 12) {
                    queue.push([nr, nc, d + 1]);
                  }
                });
              }
            }

            return result;
          },

          // 检查单个地块是否符合条件
          checkCondition({ r, c }, target) {
            const cell = this.gameMap[r][c];
            return Object.entries(target).every(([key, value]) => {
              return cell[key] === value;
            });
          },

          async autoHighlightIntersection() {
            // 1. 检查所有玩家是否只剩一个线索
            const allPlayers = [this.selfPlayer, ...this.otherPlayers];
            // 获取每个玩家的剩余线索
            for (const player of allPlayers) {
              if (this.remainingCluesCount[player] !== 1) return; // 如果任意玩家线索数≠1则终止
            }

            // 2. 获取所有线索对应的坐标
            const cluePromises = Object.entries(this.remainingClues).map(async ([player, clue]) => {
              const isOpposite = Object.keys(this.clues[player].opposite).includes(clue.key);
              return this.getClueCoordinates(clue, isOpposite);
            });

            const clueAreas = await Promise.all(cluePromises);

            // 3. 计算交集
            const intersection = clueAreas.reduce((acc, coords) => {
              const currentSet = new Set(coords.map(({ r, c }) => `${r},${c}`));
              if (!acc) return currentSet;
              return new Set([...acc].filter((x) => currentSet.has(x)));
            }, null);

            // 4. 高亮显示
            if (intersection && intersection.size > 0) {
              this.highlightedCells = Array.from(intersection).map((s) => {
                const [r, c] = s.split(',').map(Number);
                return { r, c };
              });
              alert(`已找到${intersection.size}个潜在目标位置！`);
            } else {
              alert('警告：线索矛盾，没有交集位置！');
            }
          },

          // 关闭行动弹窗
          closeActionDialog() {
            this.showActionDialog = false;
            this.selectedHex = null;
            this.selectedAction = { player: '', token: '' };
          },

          // 寻找最优放置点（仅针对当前玩家）
          findOptimalPlacement() {
            let minExposure = Infinity;
            let bestSpots = [];
            const currentPlayer = this.selfPlayer;
            const isOpposite = this.selfClue.startsWith('opposite-');
            this.gameMap.forEach((row, r) => {
              row.forEach((cell, c) => {
                // 有我自己的token或有方块的地块不能再我我自己的token
                if (cell.tokens?.some((t) => t.player == currentPlayer || t.type === 'square')) return;

                const selfClue = this.clues[currentPlayer][isOpposite ? 'opposite' : 'normal'][this.selfClue.replace('opposite-', '')];
                // 获取线索对应的六边形
                const hexagons = this.getHexagons(r, c, selfClue.distance);
                const found = hexagons.some((hex) => selfClue.targets.some((target) => this.checkCondition({ r, c }, target)));
                // 判断这个是不是我的线索，如果是，则不能放方块
                if ((found && !isOpposite) || (!found && isOpposite)) return;
                // 克隆当前玩家的线索（包含专家模式）
                const tempClues = {
                  normal: JSON.parse(
                    JSON.stringify(
                      Object.values(this.clues[currentPlayer].normal).filter((clue) => {
                        return !clue.exclude;
                      })
                    )
                  ),
                  opposite: this.expertMode
                    ? JSON.parse(
                        JSON.stringify(
                          Object.values(this.clues[currentPlayer].normal).filter((clue) => {
                            return !clue.exclude;
                          })
                        )
                      )
                    : null,
                };

                // 模拟排除逻辑
                this.excludeCluesSimulated(r, c, currentPlayer, 'square', tempClues);

                // 计算总暴露线索数（包含反向线索）
                const exposure = [...Object.values(tempClues.normal), ...(this.expertMode ? Object.values(tempClues.opposite) : [])].filter((c) => c.exclude).length;
                // 更新最佳位置
                if (exposure < minExposure) {
                  minExposure = exposure;
                  bestSpots = [{ r, c }];
                } else if (exposure === minExposure) {
                  bestSpots.push({ r, c });
                }
              });
            });

            return bestSpots.map((spot) => ({
              ...spot,
              exposure: minExposure,
            }));
          },
          // 显示推荐询问点层
          showAskPoints(player) {
            const spots = this.findAskPlacement(player);
            this.recommendedAskPoints = spots;
            this.maxExpose = spots[0]?.exposure || 0;
            this.showAskPointsDialog = true;
          },
          // 模拟最优询问点
          findAskPlacement(player) {
            let maxExposure = 0;
            let bestSpots = [];
            const currentPlayer = player;

            this.gameMap.forEach((row, r) => {
              row.forEach((cell, c) => {
                // 有我自己的token或有方块的地块不能再我我自己的token
                if (cell.tokens?.some((t) => t.player == currentPlayer || t.type === 'square')) return;

                // const isOpposite = this.selfClue.startsWith('opposite-');
                // const selfClue = this.clues[currentPlayer][isOpposite ? 'opposite' : 'normal'][this.selfClue.replace('opposite-', '')];
                // 获取线索对应的六边形
                // const hexagons = this.getHexagons(r, c, selfClue.distance);
                // const found = hexagons.some((hex) => selfClue.targets.some((target) => this.checkCondition({ r, c }, target)));
                // 判断这个是不是我的线索，如果是，则不能放方块
                // if ((found && !isOpposite) || (!found && isOpposite)) return;

                // 当前玩家的剩余线索（包含专家模式）
                const tempClues1 = {
                  normal: JSON.parse(
                    JSON.stringify(
                      Object.values(this.clues[currentPlayer].normal).filter((clue) => {
                        return !clue.exclude;
                      })
                    )
                  ),
                  opposite: this.expertMode
                    ? JSON.parse(
                        JSON.stringify(
                          Object.values(this.clues[currentPlayer].opposite).filter((clue) => {
                            return !clue.exclude;
                          })
                        )
                      )
                    : null,
                };
                const tempClues2 = JSON.parse(JSON.stringify(tempClues1));

                // 模拟排除逻辑
                this.excludeCluesSimulated(r, c, currentPlayer, 'square', tempClues1);
                // 计算总暴露线索数（包含反向线索）
                const exposure1 = [...Object.values(tempClues1.normal), ...(this.expertMode ? Object.values(tempClues1.opposite) : [])].filter((c) => c.exclude).length;

                this.excludeCluesSimulated(r, c, currentPlayer, 'circle', tempClues2);
                // 计算总暴露线索数（包含反向线索）
                const exposure2 = [...Object.values(tempClues2.normal), ...(this.expertMode ? Object.values(tempClues2.opposite) : [])].filter((c) => c.exclude).length;

                const _max = Math.max(exposure1, exposure2);
                const _min = Math.min(exposure1, exposure2);
                // 更新最佳位置
                if (_max > maxExposure) {
                  maxExposure = _max;
                  bestSpots = [{ r, c, min: _min }];
                } else if (_max === maxExposure) {
                  bestSpots.push({ r, c, min: _min });
                }
              });
            });

            return bestSpots.map((spot) => ({
              ...spot,
              exposure: maxExposure,
            }));
          },
          // 模拟排除逻辑方法
          excludeCluesSimulated(row, col, player, type, tempClues) {
            // 根据isOpposite参数，获取对应的线索
            const processClues = (isOpposite) => {
              const clues = isOpposite ? tempClues.opposite : tempClues.normal;
              // 遍历线索
              Object.values(clues).forEach((clue) => {
                // 如果线索有排除条件，则跳过
                if (clue.exclude) return;

                // 获取线索对应的六边形
                const hexagons = this.getHexagons(row, col, clue.distance);
                // 判断线索是否满足条件
                const found = hexagons.some((hex) => clue.targets.some((target) => this.checkCondition({ r: hex.row, c: hex.col }, target)));

                // 判断排除条件
                if (isOpposite) {
                  // 如果是反线索，则根据type判断是否排除
                  if ((found && type === 'circle') || (!found && type === 'square')) {
                    clue.exclude = true;
                    console.log(`行: ${row}, 列: ${col}, 玩家: ${player}, 类型: ${type}, 排除线索: 不在 ${clue.desc}`);
                  }
                } else {
                  // 如果是正线索，则根据type判断是否排除
                  if ((found && type === 'square') || (!found && type === 'circle')) {
                    clue.exclude = true;
                    console.log(`行: ${row}, 列: ${col}, 玩家: ${player}, 类型: ${type}, 排除线索: ${clue.desc}`);
                  }
                }
              });
            };

            // 处理普通线索
            processClues(false);
            // 处理反向线索（如果专家模式）
            if (this.expertMode) processClues(true);
          },

          // 选中或反选线索
          toggleClue(player, clueKey, event) {
            event.preventDefault();

            // 处理反向线索和排除检查
            const isOpposite = clueKey.startsWith('opposite-');
            const realKey = isOpposite ? clueKey.replace('opposite-', '') : clueKey;
            const clueType = isOpposite ? 'opposite' : 'normal';
            const clueData = this.clues[player][clueType][realKey];

            // 如果线索已被排除则直接返回
            if (clueData?.exclude) return;

            // 获取当前选择状态
            const currentSelected = this.selectedClues[player];

            // 如果是取消选择操作
            if (currentSelected === clueKey) {
              this.selectedClues[player] = null;
              return;
            }

            // 检查其他玩家是否已选择该线索
            const conflictPlayers = Object.entries(this.selectedClues)
              .filter(([p]) => p !== player) // 排除当前玩家
              .filter(([, key]) => key === clueKey) // 查找相同线索
              .map(([p]) => p); // 提取冲突玩家

            if (conflictPlayers.length > 0) {
              alert(`玩家 ${conflictPlayers.join(', ')} 已选择该线索，不可重复选择！`);
              return;
            }

            // 更新选择状态
            this.selectedClues[player] = clueKey;
          },
          // 显示线索交集
          async showCluesIntersection() {
            try {
              // 获取所有选中线索（包含自己和其他玩家）
              const cluesToCheck = [
                // 其他玩家的线索
                ...Object.entries(this.selectedClues)
                  .filter(([_, value]) => value)
                  .map(([player, clueKey]) => ({
                    player,
                    isOpposite: clueKey.startsWith('opposite-'),
                    realKey: clueKey.replace('opposite-', ''),
                  })),
                // 自己的线索
                ...(this.selfClue
                  ? [
                      {
                        player: this.selfPlayer,
                        isOpposite: this.selfClue.startsWith('opposite-'),
                        realKey: this.selfClue.replace('opposite-', ''),
                      },
                    ]
                  : []),
              ].map(({ player, isOpposite, realKey }) => {
                return {
                  player,
                  isOpposite,
                  clue: isOpposite ? this.clues[player].opposite[realKey] : this.clues[player].normal[realKey],
                };
              });

              // 计算每个线索对应的坐标集
              const allSets = await Promise.all(
                cluesToCheck.map(async ({ clue, isOpposite }) => {
                  return new Set((await this.getClueCoordinates(clue, isOpposite)).map(({ r, c }) => `${r},${c}`));
                })
              );

              // 计算交集
              const intersection = allSets.reduce((acc, set) => {
                if (!acc) return set;
                return new Set([...acc].filter((x) => set.has(x)));
              }, null);

              // 高亮显示
              this.highlightedCells = intersection
                ? Array.from(intersection).map((s) => {
                    const [r, c] = s.split(',');
                    return { r: parseInt(r), c: parseInt(c) };
                  })
                : [];
              this.showCluePanel = false;
            } catch (error) {
              console.error('显示线索交集错误:', error);
              this.highlightedCells = [];
            }
          },

          // 获取单个线索对应的坐标
          async getClueCoordinates(clue, isOpposite = false) {
            // 获取全地图坐标
            const allCoords = this.gameMap.flatMap((row, r) => row.map((_, c) => ({ r, c }))).map(({ r, c }) => `${r},${c}`);

            // 处理正向逻辑
            const processPositive = async () => {
              const coordinates = new Set();
              // 步骤1：找到所有符合targets的源地块
              const sourceCells = [];

              this.gameMap.forEach((row, r) => {
                row.forEach((cell, c) => {
                  if (clue.targets.some((target) => this.checkCondition({ r, c }, target))) {
                    sourceCells.push({ r, c });
                  }
                });
              });

              // 步骤2：对每个源地块获取周围区域
              sourceCells.forEach(({ r, c }) => {
                const hexagons = this.getHexagons(r, c, clue.distance);
                hexagons.forEach(({ row, col }) => {
                  // 添加边界检查
                  if (row >= 0 && row < 9 && col >= 0 && col < 12) {
                    coordinates.add(`${row},${col}`);
                  }
                });
              });
              return coordinates;
            };

            // 获取正向结果
            const positiveSet = await processPositive();

            // 处理反向逻辑
            if (isOpposite) {
              const oppositeSet = new Set(allCoords.filter((coord) => !positiveSet.has(coord)));
              return Array.from(oppositeSet).map((s) => {
                const [r, c] = s.split(',').map(Number);
                return { r, c };
              });
            }

            // 返回正向结果，转换回坐标对象
            return Array.from(positiveSet).map((s) => {
              const [r, c] = s.split(',').map(Number);
              return { r, c };
            });
          },
          isHighlighted(r, c) {
            return this.highlightedCells?.some((cell) => cell.r === r && cell.c === c);
          },
          getClueDescription(player, clueKey) {
            const isOpposite = clueKey.startsWith('opposite-');
            const realKey = isOpposite ? clueKey.replace('opposite-', '') : clueKey;
            const clueType = isOpposite ? 'opposite' : 'normal';
            const clue = this.clues[player][clueType][realKey];

            if (!clue) return '未知线索';
            return isOpposite ? `[不在] ${clue.desc}` : clue.desc;
          },
          // 显示推荐位置层
          showRecommendation() {
            const spots = this.findOptimalPlacement();
            this.recommendedSpots = spots;
            this.recommendationExposure = spots[0]?.exposure || 0;
            this.showRecommendationDialog = true;
          },

          formatCoordinates(r, c) {
            // 行列从1开始计数更符合用户习惯
            return `行 ${r + 1} - 列 ${c + 1}`;
          },
          // 清除高亮
          clearHighlight() {
            this.highlightedCells = [];
          },
          highlightSpot(spot) {
            // 清除旧高亮
            this.clearHighlight();
            // 添加新高亮
            this.$nextTick(() => {
              this.highlightedCells = [{ r: spot.r, c: spot.c }];
            });
            this.showRecommendationDialog = false;
            this.showAskPointsDialog = false;
            this.showCluePanel = false;
          },
        },
      });
    </script>
  </body>
</html>
